use crate::debugln;
use crate::Result;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::String;
use core::ffi::{c_int, c_void};
use core::mem;
use core::ptr::null_mut;

pub fn sleep_ms(ms: u32) {
    unsafe {
        if ms < 20 {
            let mut tv: libc::timeval = core::mem::zeroed();
            tv.tv_sec = 0;
            tv.tv_usec = ms as libc::suseconds_t * 1000;
            libc::select(0, null_mut(), null_mut(), null_mut(), &mut tv);
        } else {
            libc::usleep(ms * 1000);
        }
    }
}

struct ThreadArgs {
    pub(crate) name: String,
    pub(crate) func: Box<dyn FnOnce() + Send + 'static>,
}

pub struct Thread {
    id: libc::pthread_t,
    joined: bool,
}

impl Thread {
    pub fn new<F>(stack_size: usize, f: F) -> Result<Self>
    where
        F: FnOnce(),
        F: Send + 'static,
    {
        Self::new_with_name(stack_size, "rthread", f)
    }

    pub fn new_with_name<F>(stack_size: usize, name: &str, f: F) -> Result<Self>
    where
        F: FnOnce(),
        F: Send + 'static,
    {
        unsafe {
            let mut attr: libc::pthread_attr_t = mem::zeroed();
            libc::pthread_attr_init(&mut attr);
            if stack_size != 0 {
                libc::pthread_attr_setstacksize(&mut attr, stack_size as libc::size_t);
            }
            let args = Box::new(ThreadArgs {
                name: String::from(name),
                func: Box::new(f),
            });
            let p = Box::into_raw(args);
            debugln!("thread args {:p}", p);
            let mut tid: libc::pthread_t = core::mem::zeroed();
            let ret = libc::pthread_create(&mut tid, &attr, __rust_thread_entry, p as *mut c_void);
            if ret != 0 {
                let _p1 = Box::from(p);
                return Err(crate::errors::Error::Other(format!(
                    "start thread failed: {}",
                    ret
                )));
            }
            Ok(Self {
                id: tid,
                joined: false,
            })
        }
    }

    pub fn join(mut self) -> Result<()> {
        debugln!("join thread {}", self.id);
        unsafe {
            let _ret = libc::pthread_join(self.id, null_mut());
            self.joined = true;
        }
        Ok(())
    }
}

impl Drop for Thread {
    fn drop(&mut self) {
        if !self.joined {
            debugln!("drop: detach thread {}", self.id);
            let _ret = unsafe { libc::pthread_detach(self.id) };
        }
    }
}

pub struct JoinHandle {
    thread: Option<Thread>,
}

impl JoinHandle {
    pub(crate) fn new(thread: Thread) -> Self {
        Self {
            thread: Some(thread),
        }
    }

    pub fn join(mut self) -> Result<()> {
        if let Some(t) = self.thread.take() {
            t.join()
        } else {
            Ok(())
        }
    }
}

impl Drop for JoinHandle {
    fn drop(&mut self) {
        debugln!("drop joinhandle");
    }
}

pub struct Builder {
    stack_size: usize,
    name: String,
}

impl Builder {
    pub fn new() -> Self {
        Self {
            stack_size: 2048,
            name: String::from("\0"),
        }
    }

    pub fn stack_size(mut self, size: usize) -> Self {
        self.stack_size = size;
        self
    }

    pub fn name(mut self, name: String) -> Self {
        self.name = name;
        self.name.push_str("\0");
        self
    }

    pub fn spawn<F>(self, f: F) -> crate::errors::Result<JoinHandle>
    where
        F: FnOnce(),
        F: Send + 'static,
    {
        let thread = Thread::new(self.stack_size, f)?;
        Ok(JoinHandle::new(thread))
    }
}

pub fn spawn<F>(f: F) -> Result<JoinHandle>
where
    F: FnOnce(),
    F: Send + 'static,
{
    Builder::new().spawn(f)
}

pub const PR_SET_NAME: c_int = 15;
extern "C" {
    pub fn prctl(__option: c_int, ...) -> c_int;
}
extern "C" fn __rust_thread_entry(args: *mut c_void) -> *mut c_void {
    debugln!("thread entry, args {:p}", args);
    if !args.is_null() {
        let args: Box<ThreadArgs> = unsafe { Box::from_raw(args as *mut _) };
        set_thread_name(args.name.as_str());
        debugln!(
            "thread name {} calling func {:p} >>>",
            args.name,
            &args.func
        );
        (args.func)();
        debugln!("thread name {}<<<", args.name);
    }
    null_mut()
}

pub fn set_thread_name(name: &str) {
    #[cfg(not(any(feature = "rtos", feature = "liteos")))]
    unsafe {
        use alloc::vec::Vec;
        use cstr_core::CString;
        let cname = CString::from_vec_unchecked(Vec::from(name));
        prctl(PR_SET_NAME, cname.as_ptr())
    };
}
